#include "Camera.h"
#include <stdio.h>
#include <NxMath.h>
#include <PhysXLoader.h>

Camera::Camera() {

}

Camera::Camera(const NxVec3& position, const NxVec3& direction, float aspectRatio) : upNormal(0, 1, 0) {
	this->position = position;
	this->direction = direction;
	this->direction.normalize();

	//default values
	this->fovyAngle = 60.0f;
	this->aspectRatio = aspectRatio;
	this->zNear = 0.1f;
	this->zFar = 128000.0f;
}

Camera::Camera(const NxVec3& position, const NxVec3& direction, float aspectRatio, float fovyAngle, float zNear, float zFar) : upNormal(0, 1, 0) {
	this->position = position;
	this->direction = direction;
	this->direction.normalize();
	this->aspectRatio = aspectRatio;
	this->fovyAngle = fovyAngle;	
	this->zNear = zNear;
	this->zFar = zFar;
}

Camera::~Camera() {

}

//Setters

void Camera::SetPosition(const NxVec3& pos) {
	this->position = pos;
}

void Camera::SetDirection(const NxVec3& direction) {
	this->direction = direction;
	this->direction.normalize();
}

void Camera::SetFovyAngle(float fovyAngle) {
	this->fovyAngle = fovyAngle;
}

void Camera::SetAspectRatio(float aspectRatio) {
	this->aspectRatio = aspectRatio;
}

void Camera::SetzNear(float zNear) {
	this->zNear = zNear;
}

void Camera::SetzFar(float zFar) {
	this->zFar = zFar;
}

//Getters

const NxVec3& Camera::GetPosition() const {
	return this->position;
}

const NxVec3& Camera::GetDirection() const {
	return this->direction;
}

NxVec3 Camera::GetSideNormal() const {
	return direction.cross(GetUpNormal());
}

float Camera::GetFovyAngle() const {
	return this->fovyAngle;
}

float Camera::GetAspectRatio() const {
	return this->aspectRatio;
}

float Camera::GetzNear() const {
	return this->zNear;
}

float Camera::GetzFar() const {
	return this->zFar;
}

float Camera::GetXAngle() const {
	return atan2(this->direction.y, this->direction.z);
}

float Camera::GetYAngle() const {
	return atan2(this->direction.x, this->direction.z);
}

float Camera::GetZAngle() const {
	return atan2(this->direction.y, this->direction.x);
}

/************************************************************************************************/
/* implementation approaches from:
/* http://www.lighthouse3d.com/tutorials/view-frustum-culling/geometric-approach-implementation/
/************************************************************************************************/
NxPlane *Camera::GetPlanesOfView() const{
    NxVec3 dir = direction;
    NxVec3 pos = position;
    NxVec3 zAxis = dir;
    zAxis.setNegative();
    NxVec3 xAxis = upNormal.cross(zAxis);
    xAxis.normalize();
    NxVec3 yAxis = zAxis.cross(xAxis);

    float tan = NxMath::tan(NxPiF32/180.0f * fovyAngle * 0.5f);
    float nearHeight = tan * zNear;
    float nearWidth = aspectRatio * nearHeight;

    float farHeight = tan * zFar;
    float farWidth = aspectRatio * farHeight;

    NxVec3 nearCenter = pos + dir * zNear;
    NxVec3 farCenter = pos + dir * zFar;

    NxPlane *planes = new NxPlane[6];
    planes[0] = NxPlane(nearCenter, zAxis); //near
    planes[1] = NxPlane(farCenter, dir); //far

    NxVec3 tmp,normal;

    tmp = (nearCenter + yAxis*nearHeight) - pos;
    tmp.normalize();
    normal = xAxis.cross(tmp);
    planes[2] = NxPlane(nearCenter + yAxis*nearHeight, normal); //top

    tmp = (nearCenter - yAxis*nearHeight) - pos;
    tmp.normalize();
    normal = tmp.cross(xAxis);
    planes[3] = NxPlane(nearCenter - yAxis*nearHeight, normal); //bottom

    tmp = (nearCenter - xAxis*nearWidth) - pos;
    tmp.normalize();
    normal = yAxis.cross(tmp);
    planes[4] = NxPlane(nearCenter - xAxis*nearWidth, normal); //left

    tmp = (nearCenter + xAxis*nearWidth) - pos;
    tmp.normalize();
    normal = tmp.cross(yAxis);
    planes[5] = NxPlane(nearCenter + xAxis*nearWidth, normal); //right

    return planes;
}

//Operations

void Camera::MoveBy(const NxVec3& d) {
	this->position += d;
}

void Camera::Rotate(float xAngle, float yAngle) {
	const float xa = NxPiF32 * -xAngle / 180.0f;
	const float ya = NxPiF32 * -yAngle / 180.0f;

	NxQuat qx(xa, NxVec3(0,1,0));
	qx.rotate(this->direction);

	NxQuat qy(ya, GetSideNormal());
	qy.rotate(this->direction);
}
